www.gusucode.com > Ext 2.0 JSP源码程序 > Ext 2.0 JSP/AJAX开发包/gwtext2.0.5/gwtext-2.0.5/samples/Maps/www/com.gwtext.sample.maps.Maps/js/livegrid/BufferedGridView.js
/* * Ext.ux.grid.BufferedGridView V0.1 * Copyright(c) 2007, http://www.siteartwork.de * * Licensed under the terms of the Open Source LGPL 3.0 * http://www.gnu.org/licenses/lgpl.html * * @author Thorsten Suckow-Homberg <ts@siteartwork.de> */ /** * @class Ext.ux.grid.BufferedGridView * @extends Ext.grid.GridView * * * @constructor * @param {Object} config */ Ext.namespace('Ext.ux.grid'); Ext.ux.grid.BufferedGridView = function(config) { this.addEvents({ /** * @event beforebuffer * Fires when the store is about to buffer new data. * @param {Ext.ux.BufferedGridView} this * @param {Ext.data.Store} store The store * @param {Number} rowIndex * @param {Number} visibleRows * @param {Number} totalCount */ 'beforebuffer' : true, /** * @event buffer * Fires when the store is finsihed buffering new data. * @param {Ext.ux.BufferedGridView} this * @param {Ext.data.Store} store The store * @param {Number} rowIndex * @param {Number} visibleRows * @param {Number} totalCount */ 'buffer' : true, /** * @event cursormove * Fires when the the user scrolls through the data. * @param {Ext.ux.BufferedGridView} this * @param {Number} rowIndex The index of the first visible row in the * grid absolute to it's position in the model. * @param {Number} visibleRows The number of rows visible in the grid. * @param {Number} totalCount */ 'cursormove' : true }); /** * @cfg {Number} bufferSize The number of records that will at least always * be available in the store for rendering. This value will be send to the * server as the <tt>limit</tt> parameter and should not change during the * lifetime of a grid component. Note: In a paging grid, this number would * indicate the page size. * The value should be set high enough to make a userfirendly scrolling * possible and should be greater than the sum of {nearLimit} and * {visibleRows}. Usually, a value in between 150 and 200 is good enough. * A lesser value will more often make the store re-request new data, while * a larger number will make loading times higher. */ /** * @cfg {Number} nearLimit This value represents a near value that is responsible * for deciding if a request for new data is needed. The lesser the number, the * more often new data will be requested. The number should be set to a value * that lies in between 1/4 to 1/2 of the {bufferSize}. */ /** * @cfg {Number} horizontalScrollOffset The height of a horizontal aligned * scrollbar. The scrollbar is shown if the total width of all visible * columns exceeds the width of the grid component. * On Windows XP (IE7, FF2), this value defaults to 16. */ this.horizontalScrollOffset = 16; /** * @cfg {Object} loadMaskConfig The config of the load mask that will be shown * by the view if a request for new data is underway. */ this.loadMask = false; Ext.apply(this, config); /** * The array represents the range of rows available in the buffer absolute to * the indexes of the data model. * @param {Array} */ this.bufferRange = [0, -1]; this.templates = {}; /** * The master template adds an addiiotnal scrollbar to make cursoring in the * data possible. */ this.templates.master = new Ext.Template( '<div class="x-grid3" hidefocus="true"><div style="z-index:2000;background:none;position:relative;height:321px; float:right; width: 18px;overflow: scroll;"><div style="background:none;width:1px;overflow:hidden;font-size:1px;height:0px;"></div></div>', '<div class="x-grid3-viewport" style="float:left">', '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset">{header}</div></div><div class="x-clear"></div></div>', '<div class="x-grid3-scroller" style="overflow-y:hidden !important;"><div class="x-grid3-body" style="position:relative;">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>', "</div>", '<div class="x-grid3-resize-marker"> </div>', '<div class="x-grid3-resize-proxy"> </div>', "</div>" ); Ext.ux.grid.BufferedGridView.superclass.constructor.call(this); }; Ext.extend(Ext.ux.grid.BufferedGridView, Ext.grid.GridView, { // {{{ --------------------------properties------------------------------------- /** * This is the actual y-scroller that does control sending request to the server * based upon the position of the scrolling cursor. * @param {Ext.Element} */ liveScroller : null, /** * This is the panel that represents the amount of data in a given repository. * The height gets computed via the total amount of records multiplied with * the fixed(!) row height * @param {native HTMLObject} */ liveScrollerInset : null, /** * The <b>fixed</b> row height for <b>every</b> row in the grid. The value is * computed once the store has been loaded for the first time and used for * various calculations during the lifetime of the grid component, such as * the height of the scroller and the number of visible rows. * @param {Number} */ rowHeight : -1, /** * Stores the number of visible rows that have to be rendered. * @param {Number} */ visibleRows : 1, /** * Stores the last offset relative to a previously scroll action. This is * needed for deciding wether the user scrolls up or down. * @param {Number} */ lastIndex : -1, /** * Stores the last visible row at position "0" in the table view before * a new scroll event was created and fired. * @param {Number} */ lastRowIndex : 0, /** * Stores the value of the <tt>liveScroller</tt>'s <tt>scrollTop</tt> DOM * property. * @param {Number} */ lastScrollPos : 0, /** * The current index of the row in the model that is displayed as the first * visible row in the view. * @param {Number} */ rowIndex : 0, /** * Set to <tt>true</tt> if the store is busy with loading new data. * @param {Boolean} */ isBuffering : false, /** * If a request for new data was made and the user scrolls to a new position * that lays not within the requested range of the new data, the queue will * hold the latest requested position. If the buffering succeeds and the value * of requestQueue is not within the range of the current buffer, data may be * re-requested. * * @param {Number} */ requestQueue : -1, /** * The view's own load mask that will be shown when a request to data was made * and there are no rows in the buffer left to render. * @see {loadMaskConfig} * @param {Ext.LoadMask} */ loadMask : null, /** * Set to <tt>true</tt> if a request for new data has been made while there * are still rows in the buffer that can be rendered before the request * finishes. * @param {Boolean} */ isPrebuffering : false, // }}} // {{{ --------------------------public API methods----------------------------- /** * Resets the view to display the first row in the data model. This will * change the scrollTop property of the scroller and may trigger a request * to buffer new data, if the row index "0" is not within the buffer range and * forceReload is set to true. * * @param {Boolean} forceReload <tt>true</tt> to reload the buffers contents, * othwerwise <tt>false</tt> */ reset : function(forceReload) { if (forceReload === false) { this.ds.modified = []; this.grid.selModel.clearSelections(true); this.rowIndex = 0; this.lastScrollPos = 0; this.lastRowIndex = 0; this.lastIndex = 0; this.bufferRange = [0, this.ds.bufferSize]; this.adjustScrollerPos(-this.liveScroller.dom.scrollTop, true); this.showLoadMask(false); this.refresh(true); //this.replaceLiveRows(0, true); this.fireEvent('cursormove', this, 0, Math.min(this.ds.totalLength, this.visibleRows), this.ds.totalLength); } else { var params = { start : 0, limit : this.ds.bufferSize }; var sInfo = this.ds.sortInfo; if (sInfo) { params.dir = sInfo.direction; params.sort = sInfo.field; } this.ds.load({ callback : function(){this.reset(false);}, scope : this, params : params }); } }, // {{{ ------------adjusted methods for applying custom behavior---------------- /** * Overwritten so the {@link Ext.ux.grid.BufferedGridDragZone} can be used * with this view implementation. * * Since detaching a previously created DragZone from a grid panel seems to * be impossible, a little workaround will tell the parent implementation * that drad/drop is not enabled for this view's grid, and right after that * the custom DragZone will be created, if neccessary. */ renderUI : function() { var g = this.grid; var dEnabled = g.enableDragDrop || g.enableDrag; g.enableDragDrop = false; g.enableDrag = false; Ext.ux.grid.BufferedGridView.superclass.renderUI.call(this); var g = this.grid; g.enableDragDrop = dEnabled; g.enableDrag = dEnabled; if(dEnabled){ var dd = new Ext.ux.grid.BufferedGridDragZone(g, { ddGroup : g.ddGroup || 'GridDD' }); } if (this.loadMask) { this.loadMask = new Ext.LoadMask( this.mainBody.dom.parentNode.parentNode, this.loadMask ); } }, /** * The extended implementation attaches an listener to the beforeload * event of the store of the grid. It is guaranteed that the listener will * only be executed upon reloading of the store, sorting and initial loading * of data. When the store does "buffer", all events are suspended and the * beforeload event will not be triggered. * * @param {Ext.grid.GridPanel} grid The grid panel this view is attached to */ init: function(grid) { Ext.ux.grid.BufferedGridView.superclass.init.call(this, grid); this.bufferRange = [0, this.ds.bufferSize]; this.ds.on('beforeload', this.onBeforeLoad, this); }, /** * Only render the viewable rect of the table. The number of rows visible to * the user is defined in <tt>visibleRows</tt>. * This implementation does completely overwrite the parent's implementation. */ // private renderBody : function() { var markup = this.renderRows(0, this.visibleRows-1); return this.templates.body.apply({rows: markup}); }, /** * Inits the DOM native elements for this component. * The properties <tt>liveScroller</tt> and <tt>liveScrollerInset</tt> will * be respected as provided by the master template. * The <tt>scroll</tt> listener for the <tt>liverScroller</tt> will also be * added here as the <tt>mousewheel</tt> listener. * This method overwrites the parents implementation. */ // private initElements : function() { var E = Ext.Element; var el = this.grid.getGridEl().dom.firstChild; var cs = el.childNodes; this.el = new E(el); this.mainWrap = new E(cs[1]); // liveScroller and liveScrollerInset this.liveScroller = new E(cs[0]); this.liveScrollerInset = this.liveScroller.dom.firstChild; this.liveScroller.on('scroll', this.onLiveScroll, this); this.mainHd = new E(this.mainWrap.dom.firstChild); this.innerHd = this.mainHd.dom.firstChild; this.scroller = new E(this.mainWrap.dom.childNodes[1]); if(this.forceFit){ this.scroller.setStyle('overflow-x', 'hidden'); } this.mainBody = new E(this.scroller.dom.firstChild); // addd the mousewheel event to the table's body this.mainBody.on('mousewheel', this.handleWheel, this); this.focusEl = new E(this.scroller.dom.childNodes[1]); this.focusEl.swallowEvent("click", true); this.resizeMarker = new E(cs[2]); this.resizeProxy = new E(cs[3]); }, /** * Layouts the grid's view taking the scroller into account. The height * of the scroller gets adjusted depending on the total width of the columns. * The width of the grid view will be adjusted so the header and the rows do * not overlap the scroller. * This method will also compute the row-height based on the first row this * grid displays and will adjust the number of visible rows if a resize * of the grid component happened. * This method overwrites the parents implementation. */ //private layout : function() { if(!this.mainBody){ return; // not rendered } var g = this.grid; var c = g.getGridEl(), cm = this.cm, expandCol = g.autoExpandColumn, gv = this; var csize = c.getSize(true); // set vw to 19 to take scrollbar width into account! var vw = csize.width-this.scrollOffset; if(vw < 20 || csize.height < 20){ // display: none? return; } if(g.autoHeight){ this.scroller.dom.style.overflow = 'visible'; }else{ this.el.setSize(csize.width, csize.height); var hdHeight = this.mainHd.getHeight(); var vh = csize.height - (hdHeight); this.scroller.setSize(vw, vh); if(this.innerHd){ this.innerHd.style.width = (vw)+'px'; } } if(this.forceFit){ if(this.lastViewWidth != vw){ this.fitColumns(false, false); this.lastViewWidth = vw; } }else { this.autoExpand(); } // adjust the number of visible rows and the height of the scroller. this.adjustVisibleRows(); this.adjustBufferInset(); this.onLayout(vw, vh); }, // {{{ ----------------------dom/mouse listeners-------------------------------- /** * Called when a column width has been updated. Adjusts the scroller height * and the number of visible rows wether the horizontal scrollbar is shown * or not. */ onColumnWidthUpdated : function(col, w, tw) { this.adjustVisibleRows(); this.adjustBufferInset(); }, /** * Called when the width of all columns has been updated. Adjusts the scroller * height and the number of visible rows wether the horizontal scrollbar is shown * or not. */ onAllColumnWidthsUpdated : function(ws, tw) { this.adjustVisibleRows(); this.adjustBufferInset(); }, /** * Callback for selecting a row. The index of the row is the absolute index * in the datamodel and gets translated to the index in the view. * Overwrites the parent's implementation. */ // private onRowSelect : function(row) { if (row < this.rowIndex || row > this.rowIndex+this.visibleRows) { return; } var viewIndex = row-this.rowIndex; this.addRowClass(viewIndex, "x-grid3-row-selected"); }, /** * Callback for deselecting a row. The index of the row is the absolute index * in the datamodel and gets translated to the index in the view. * Overwrites the parent's implementation. */ // private onRowDeselect : function(row) { if (row < this.rowIndex || row > this.rowIndex+this.visibleRows) { return; } var viewIndex = row-this.rowIndex; this.removeRowClass(viewIndex, "x-grid3-row-selected"); }, /** * Callback for selecting a cell. The index of the row is the absolute index * in the datamodel and gets translated to the index in the view. * Overwrites the parent's implementation. */ // private onCellSelect : function(row, col) { if (row < this.rowIndex || row > this.rowIndex+this.visibleRows) { return; } var viewIndex = row-this.rowIndex; var cell = this.getCell(viewIndex, col); if(cell){ this.fly(cell).addClass("x-grid3-cell-selected"); } }, /** * Callback for deselecting a cell. The index of the row is the absolute index * in the datamodel and gets translated to the index in the view. * Overwrites the parent's implementation. */ // private onCellDeselect : function(row, col) { if (row < this.rowIndex || row > this.rowIndex+this.visibleRows) { return; } var viewIndex = row-this.rowIndex; var cell = this.getCell(viewIndex, col); if(cell){ this.fly(cell).removeClass("x-grid3-cell-selected"); } }, /** * Callback for onmouseover event of the grid's rows. The index of the row is * the absolute index in the datamodel and gets translated to the index in the * view. * Overwrites the parent's implementation. */ // private onRowOver : function(e, t) { var row; if((row = this.findRowIndex(t)) !== false){ var viewIndex = row-this.rowIndex; this.addRowClass(viewIndex, "x-grid3-row-over"); } }, /** * Callback for onmouseout event of the grid's rows. The index of the row is * the absolute index in the datamodel and gets translated to the index in the * view. * Overwrites the parent's implementation. */ // private onRowOut : function(e, t) { var row; if((row = this.findRowIndex(t)) !== false && row !== this.findRowIndex(e.getRelatedTarget())){ var viewIndex = row-this.rowIndex; this.removeRowClass(viewIndex, "x-grid3-row-over"); } }, // {{{ ----------------------data listeners------------------------------------- /** * Called when the buffer gets cleared. Simply calls the updateLiveRows method * with the adjusted index and should force the store to reload */ // private onClear : function() { this.reset(false);//var newIndex = Math.max(this.bufferRange[0] - this.visibleRows, 0); //this.updateLiveRows(newIndex, true, true); }, /** * Callback for the underlying store's remove method. The current * implementation does only remove the selected row which record is in the * current store. * */ // private onRemove : function(ds, record, index, isUpdate) { if (index == Number.MIN_VALUE || index == Number.MAX_VALUE) { this.fireEvent("beforerowremoved", this, index, record); this.fireEvent("rowremoved", this, index, record); this.adjustBufferInset(); return; } var viewIndex = index + this.bufferRange[0]; if(isUpdate !== true){ this.fireEvent("beforerowremoved", this, viewIndex, record); } var domLength = this.getRows().length; if (viewIndex < this.rowIndex) { // if the according row is not displayed within the visible rect of // the grid, just adjust the row index and the liveScroller this.rowIndex--; this.lastRowIndex = this.rowIndex; this.adjustScrollerPos(-this.rowHeight, true); this.fireEvent('cursormove', this, this.rowIndex, Math.min(this.ds.totalLength, this.visibleRows), this.ds.totalLength); } else if (viewIndex >= this.rowIndex && viewIndex < this.rowIndex+domLength) { var lastPossibleRIndex = this.rowIndex-this.bufferRange[0]+this.visibleRows; var cInd = viewIndex-this.rowIndex; var rec = this.ds.getAt(lastPossibleRIndex); // did we reach the end of the buffer range? if (rec == null) { // are there more records we could use? send a buffer request if (this.ds.totalLength > this.rowIndex+this.visibleRows) { if(isUpdate !== true){ this.fireEvent("rowremoved", this, viewIndex, record); } this.updateLiveRows(this.rowIndex, true, true); return; } else { // no more records, neither in the underlying data model // nor in the data store if (this.rowIndex == 0) { // simply remove the row from the dom this.removeRows(cInd, cInd); } else { // scroll a new row in the rect so the whole rect is filled // with rows this.rowIndex--; if (this.rowIndex < this.bufferRange[0]) { // buffer range is invalid! request new data if(isUpdate !== true){ this.fireEvent("rowremoved", this, viewIndex, record); } this.updateLiveRows(this.rowIndex); return; } else { // still records in the store, simply update the dom this.replaceLiveRows(this.rowIndex); } } } } else { // the record is right within the visible rect of the grid. // remove the row that represents the record and append another // record from the store this.removeRows(cInd, cInd); var html = this.renderRows(lastPossibleRIndex, lastPossibleRIndex); Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html); } } // a record within the bufferrange was removed, so adjust the buffer // range this.bufferRange[1]--; this.adjustBufferInset(); if(isUpdate !== true){ this.fireEvent("rowremoved", this, viewIndex, record); } this.processRows(0, undefined, true); }, /** * The callback for the underlying data store when new data was added. * If <tt>index</tt> equals to <tt>Number.MIN_VALUE</tt> or <tt>Number.MAX_VALUE</tt>, the * method can't tell at which position in the underlying data model the * records where added. However, if <tt>index</tt> equals to <tt>Number.MIN_VALUE</tt>, * the <tt>rowIndex</tt> property will be adjusted to <tt>rowIndex+records.length</tt>, * and the <tt>liveScroller</tt>'s properties get adjusted so it matches the * new total number of records of the underlying data model. * The same will happen to any records that get added at the store index which * is currently represented by the first visible row in the view. * Any other value will cause the method to compute the number of rows that * have to be (re-)painted and calling the <tt>insertRows</tt> method, if * neccessary. * * This method triggers the <tt>beforerowsinserted</tt> and <tt>rowsinserted</tt> * event, passing the indexes of the records as they may default to the * positions in the underlying data model. However, due to the fact that * any sort algorithm may have computed the indexes of the records, it is * not guaranteed that the computed indexes equal to the indexes of the * underlying data model. * * @param {Ext.ux.grid.BufferedStore} ds The datastore that buffers records * from the underlying data model * @param {Array} records An array containing the newly added * {@link Ext.data.Record}s * @param {Number} index The index of the position in the underlying * {@link Ext.ux.grid.BufferedStore} where the rows * were added. */ // private onAdd : function(ds, records, index) { var recordLen = records.length; // values of index which equal to Number.MIN_VALUE or Number.MAX_VALUE // indicate that the records were not added to the store. The component // does not know which index those records do have in the underlying // data model if (index == Number.MAX_VALUE || index == Number.MIN_VALUE) { this.fireEvent("beforerowsinserted", this, index, index); // if index equals to Number.MIN_VALUE, shift rows! if (index == Number.MIN_VALUE) { this.rowIndex = this.rowIndex + recordLen; this.lastRowIndex = this.rowIndex; this.bufferRange[0] += recordLen; this.bufferRange[1] += recordLen; this.adjustBufferInset(); this.adjustScrollerPos(this.rowHeight*recordLen, true); this.fireEvent("rowsinserted", this, index, index); this.processRows(); // the cursor did virtually move this.fireEvent('cursormove', this, this.rowIndex, Math.min(this.ds.totalLength, this.visibleRows), this.ds.totalLength); return; } this.adjustBufferInset(); this.fireEvent("rowsinserted", this, index, index); return; } // only insert the rows which affect the current view. var start = index+this.bufferRange[0]; var end = start + (recordLen-1); var len = this.getRows().length; var firstRow = 0; var lastRow = 0; // rows would be added at the end of the rows which are currently // displayed, so fire the evnt and return if (index >= (this.rowIndex-this.bufferRange[0])+len && len == this.visibleRows) { this.fireEvent("beforerowsinserted", this, start, end); this.fireEvent("rowsinserted", this, start, end); this.adjustVisibleRows(); this.adjustBufferInset(); } // rows get added before the first row in the view else if (len == this.visibleRows && index <= this.rowIndex-this.bufferRange[0]) { this.fireEvent("beforerowsinserted", this, start, end); this.liveScroller.un('scroll', this.onLiveScroll, this); this.rowIndex += recordLen; this.lastRowIndex = this.rowIndex; this.adjustVisibleRows(); this.adjustBufferInset(); this.adjustScrollerPos(this.rowHeight*recordLen, true); this.fireEvent("rowsinserted", this, start, end); this.processRows(); this.fireEvent('cursormove', this, this.rowIndex, Math.min(this.ds.totalLength, this.visibleRows), this.ds.totalLength); } // rows get added somewhere IN the current view else if ((len < this.visibleRows ) || index > this.rowIndex-this.bufferRange[0]) { firstRow = index; lastRow = Math.min(end, this.rowIndex+this.visibleRows-1) - this.bufferRange[0]; this.insertRows(ds, firstRow, lastRow); this.adjustVisibleRows(); this.adjustBufferInset(); } }, // {{{ ----------------------store listeners------------------------------------ /** * This callback for the store's "beforeload" event will adjust the start * position and the limit of the data in the model to fetch. It is guaranteed * that this method will only be called when the store initially loads, * remeote-sorts or reloads. * All other load events will be suspended when the view requests buffer data. * See {updateLiveRows}. * * @param {Ext.data.Store} store The store the Grid Panel uses * @param {Object} options The configuration object for the proxy that loads * data from the server */ onBeforeLoad : function(store, options) { if (!options.params) { options.params = {start : 0, limit : this.ds.bufferSize}; } else { options.params.start = 0; options.params.limit = this.ds.bufferSize; } options.scope = this; options.callback = function(){this.reset(false);}; return true; }, /** * Method is used as a callback for the load-event of the attached data store. * Adjusts the buffer inset based upon the <tt>totalCount</tt> property * returned by the response. * Overwrites the parent's implementation. */ onLoad : function(o1, o2, options) { this.adjustBufferInset(); }, /** * This will be called when the data in the store has changed, i.e. a * re-buffer has occured. If the table was not rendered yet, a call to * <tt>refresh</tt> will initially render the table, which DOM elements will * then be used to re-render the table upon scrolling. * */ // private onDataChange : function(store) { this.updateHeaderSortState(); }, /** * A callback for the store when new data has been buffered successfully. * If the current row index is not within the range of the newly created * data buffer or another request to new data has been made while the store * was loading, new data will be re-requested. * * Additionally, if there are any rows that have been selected which were not * in the data store, the method will request the pending selections from * the grid's selection model and add them to the selections if available. * This is because the component assumes that a user who scrolls through the * rows and updates the view's buffer during scrolling, can check the selected * rows which come into the view for integrity. It is up to the user to * deselect those rows not matchuing the selection. * Additionally, if the version of the store changes during various requests * and selections are still pending, the versionchange event of the store * can delete the pending selections after a re-bufer happened and before this * method was called. * */ // private liveBufferUpdate : function(o1, options, o2) { this.fireEvent('buffer', this, this.ds, this.rowIndex, Math.max(this.visibleRows, this.getRows().length), this.ds.getTotalCount()); this.isBuffering = false; this.isPrebuffering = false; this.showLoadMask(false); // we have to stay in sync with rows that may have been skipped while // the request was loading. this.bufferRange = [ options.params.start, options.params.start+options.params.limit ]; var pendingSelections = this.grid.selModel.getPendingSelections(false); for (var i = 0, max_i = pendingSelections.length; i < max_i; i++) { this.grid.selModel.clearPendingSelection(pendingSelections[i]); } if (this.isInRange(this.rowIndex)) { this.replaceLiveRows(this.rowIndex); } else { this.updateLiveRows(this.rowIndex); } if (this.requestQueue >= 0) { var offset = this.requestQueue; this.requestQueue = -1; this.updateLiveRows(offset); } }, // {{{ ----------------------scroll listeners------------------------------------ /** * Handles mousewheel event on the table's body. This is neccessary since the * <tt>liveScroller</tt> element is completely detached from the table's body. * * @param {Ext.EventObject} e The event object */ handleWheel : function(e) { if (this.rowHeight == -1) { e.stopEvent(); return; } var d = e.getWheelDelta(); this.adjustScrollerPos(-(d*this.rowHeight)); e.stopEvent(); }, /** * Handles scrolling through the grid. Since the grid is fixed and rows get * removed/ added subsequently, the only way to determine the actual row in * view is to measure the <tt>scrollTop</tt> property of the <tt>liveScroller</tt>'s * DOM element. * */ onLiveScroll : function() { var scrollTop = this.liveScroller.dom.scrollTop; var pixelsSkipped = scrollTop-this.lastScrollPos; var rowsSkipped = Math.floor(pixelsSkipped/this.rowHeight); // happens when no scrolling actually happened if (pixelsSkipped == 0) { return; } var cursor = Math.floor((scrollTop)/this.rowHeight); this.rowIndex = cursor; // the lastRowIndex will be set when refreshing the view finished if (cursor == this.lastRowIndex) { return; } this.updateLiveRows(cursor, rowsSkipped); this.lastScrollPos = this.liveScroller.dom.scrollTop; }, // {{{ --------------------------helpers---------------------------------------- // private refreshRow : function(record) { var ds = this.ds, index; if(typeof record == 'number'){ index = record; record = ds.getAt(index); }else{ index = ds.indexOf(record); } var viewIndex = index + this.bufferRange[0]; if (viewIndex < this.rowIndex || viewIndex >= this.rowIndex + this.visibleRows) { this.fireEvent("rowupdated", this, viewIndex, record); return; } this.insertRows(ds, index, index, true); //this.getRow(index).rowIndex = index; //this.onRemove(ds, record, index+1, true); this.fireEvent("rowupdated", this, viewIndex, record); }, /** * Overwritten so the rowIndex can be changed to the absolute index. * * If the third parameter equals to <tt>true</tt>, the method will also * repaint the selections. */ // private processRows : function(startRow, skipStripe, paintSelections) { skipStripe = skipStripe || !this.grid.stripeRows; // we will always process all rows in the view startRow = 0; var rows = this.getRows(); var cls = ' x-grid3-row-alt '; var cursor = this.rowIndex; var index = 0; var selections = this.grid.selModel.selections; var ds = this.ds; for(var i = startRow, len = rows.length; i < len; i++){ index = i+cursor; var row = rows[i]; // changed! row.rowIndex = index; if (paintSelections == true) { if (this.grid.selModel.bufferedSelections[index] === true) { this.addRowClass(i, "x-grid3-row-selected"); selections.add(ds.getAt(index-this.bufferRange[0])); } this.fly(row).removeClass("x-grid3-row-over"); } if(!skipStripe){ var isAlt = ((i+1) % 2 == 0); var hasAlt = (' '+row.className + ' ').indexOf(cls) != -1; if(isAlt == hasAlt){ continue; } if(isAlt){ row.className += " x-grid3-row-alt"; }else{ row.className = row.className.replace("x-grid3-row-alt", ""); } } } }, /** * API only, since the passed arguments are the indexes in the buffer store. * However, the method will try to compute the indexes so they might match * the indexes of the records in the underlying data model. * */ // private insertRows : function(dm, firstRow, lastRow, isUpdate) { var viewIndexFirst = firstRow + this.bufferRange[0]; var viewIndexLast = lastRow + this.bufferRange[0]; if (!isUpdate) { this.fireEvent("beforerowsinserted", this, viewIndexFirst, viewIndexLast); } // first off, remove the rows at the bottom of the view to match the // visibleRows value and to not cause any spill in the DOM if (isUpdate !== true && this.getRows().length == this.visibleRows) { this.removeRows((this.visibleRows-1)-(lastRow-firstRow), this.visibleRows-1); } if (isUpdate) { this.removeRows(viewIndexFirst-this.rowIndex, viewIndexLast-this.rowIndex); } var html = this.renderRows(firstRow, lastRow); var before = this.getRow(firstRow-(this.rowIndex-this.bufferRange[0])); if (before) { Ext.DomHelper.insertHtml('beforeBegin', before, html); } else { Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html); } if (isUpdate === true) { var rows = this.getRows(); var cursor = this.rowIndex; for (var i = 0, max_i = rows.length; i < max_i; i++) { rows[i].rowIndex = cursor+i; } } if (!isUpdate) { this.fireEvent("rowsinserted", this, viewIndexFirst, viewIndexLast); this.processRows(firstRow); } }, /** * Focuses the specified cell. * @param {Number} row The row index * @param {Number} col The column index */ focusCell : function(row, col, hscroll) { var xy = this.ensureVisible(row, col, hscroll); if (!xy) { return; } this.focusEl.setXY(xy); if(Ext.isGecko){ this.focusEl.focus(); }else{ this.focusEl.focus.defer(1, this.focusEl); } }, /** * Makes sure that the requested /row/col is visible in the viewport. * The method may invoke a request for new buffer data and triggers the * scroll-event of the <tt>liveScroller</tt> element. * */ // private ensureVisible : function(row, col, hscroll) { if(typeof row != "number"){ row = row.rowIndex; } if(row < 0 || row >= this.ds.totalLength){ return; } col = (col !== undefined ? col : 0); var rowInd = row-this.rowIndex; if (row >= this.rowIndex+this.visibleRows) { this.adjustScrollerPos(((row-(this.rowIndex+this.visibleRows))+1)*this.rowHeight); } else if (row < this.rowIndex) { this.adjustScrollerPos((rowInd)*this.rowHeight); } var rowInd = rowInd < 0 ? row : rowInd; var rowEl = this.getRow(rowInd), cellEl; if(!(hscroll === false && col === 0)){ while(this.cm.isHidden(col)){ col++; } cellEl = this.getCell(row-this.rowIndex, col); } if(!rowEl){ return; } var c = this.scroller.dom; return cellEl ? Ext.fly(cellEl).getXY() : [c.scrollLeft, Ext.fly(rowEl).getY()]; }, /** * Checks if the passed argument <tt>cursor</tt> lays within a renderable * area. The area is renderable, if the sum of cursor and the visibleRows * property does not exceed the current upper buffer limit. * * If this method returns <tt>true</tt>, it's basically save to re-render * the view with <tt>cursor</tt> as the absolute position in the model * as the first visible row. * * @param {Number} cursor The absolute position of the row in the data model. * * @return {Boolean} <tt>true</tt>, if the row can be rendered, otherwise * <tt>false</tt> * */ isInRange : function(rowIndex) { var lastRowIndex = Math.min(this.ds.totalLength-1, rowIndex + this.visibleRows); return (rowIndex >= this.bufferRange[0]) && (lastRowIndex <= this.bufferRange[1]); }, /** * Calculates the bufferRange start index for a buffer request * * @param {Boolean} inRange If the index is within the current buffer range * @param {Number} index The index to use as a reference for the calculations * @param {Boolean} down Wether the calculation was requested when the user scrolls down */ getPredictedBufferIndex : function(index, inRange, down) { if (!inRange) { return Math.max(0, index-(2*this.nearLimit)); } if (!down) { return Math.max(0, (index-this.ds.bufferSize)+this.visibleRows); } if (down) { return Math.max(0, Math.min(index, this.ds.totalLength-this.ds.bufferSize)); } }, /** * Updates the table view. Removes/appends rows as needed and fetches the * cells content out of the available store. If the needed rows are not within * the buffer, the method will advise the store to update it's contents. * * The method puts the requested cursor into the queue if a previously called * buffering is in process. * * @param {Number} cursor The row's position, absolute to it's position in the * data model * */ updateLiveRows: function(index, forceRepaint, forceReload) { this.fireEvent('cursormove', this, index, Math.min(this.ds.totalLength, this.visibleRows), this.ds.totalLength); var inRange = this.isInRange(index); if (this.isBuffering && this.isPrebuffering) { if (inRange) { this.replaceLiveRows(index); } else { this.showLoadMask(true); } } if (this.isBuffering) { this.requestQueue = index; return; } var lastIndex = this.lastIndex; this.lastIndex = index; var inRange = this.isInRange(index); var down = false; if (inRange && forceReload !== true) { // repaint the table's view this.replaceLiveRows(index, forceRepaint); // lets decide if we can void this method or stay in here for // requesting a buffer update if (index > lastIndex) { // scrolling down down = true; var totalCount = this.ds.totalLength; // while scrolling, we have not yet reached the row index // that would trigger a re-buffer if (index+this.visibleRows+this.nearLimit < this.bufferRange[1]) { return; } // If we have already buffered the last range we can ever get // by the queried data repository, we don't need to buffer again. // This basically means that a re-buffer would only occur again // if we are scrolling up. if (this.bufferRange[1] >= totalCount) { return; } } else if (index < lastIndex) { // scrolling up down = false; // We are scrolling up in the first buffer range we can ever get // Re-buffering would only occur upon scrolling down. if (this.bufferRange[0] <= 0) { return; } // if we are scrolling up and we are moving in an acceptable // buffer range, lets return. if (index - this.nearLimit > this.bufferRange[0]) { return; } } else { return; } this.isPrebuffering = true; } // prepare for rebuffering this.isBuffering = true var bufferOffset = this.getPredictedBufferIndex(index, inRange, down); var fetchSize = this.ds.bufferSize; if (!inRange) { this.showLoadMask(true); } this.fireEvent('beforebuffer', this, this.ds, index, this.visibleRows, this.ds.totalLength); this.ds.suspendEvents(); var sInfo = this.ds.sortInfo; var params = {}; Ext.apply(params, this.ds.lastOptions); params.start = bufferOffset; params.limit = this.ds.bufferSize; if (sInfo) { params.dir = sInfo.direction; params.sort = sInfo.field; } this.ds.load({ callback : this.liveBufferUpdate, scope : this, params : params }); this.ds.resumeEvents(); }, /** * Shows this' view own load mask to indicate that a large amount of buffer * data was requested by the store. * @param {Boolean} show <tt>true</tt> to show the load mask, otherwise * <tt>false</tt> */ showLoadMask : function(show) { if (this.loadMask == null) { if (show) { this.loadMask = new Ext.LoadMask(this.mainBody.dom.parentNode.parentNode, this.loadMaskConfig); } else { return; } } if (show) { this.loadMask.show(); } else { this.loadMask.hide(); } }, /** * Renders the table body with the contents of the model. The method will * prepend/ append rows after removing from either the end or the beginning * of the table DOM to reduce expensive DOM calls. * It will also take care of rendering the rows selected, taking the property * <tt>bufferedSelections</tt> of the {@link BufferedRowSelectionModel} into * account. * Instead of calling this method directly, the <tt>updateLiveRows</tt> method * should be called which takes care of rebuffering if needed, since this method * will behave erroneous if data of the buffer is requested which may not be * available. * * @param {Number} cursor The position of the data in the model to start * rendering. * * @param {Boolean} forceReplace <tt>true</tt> for recomputing the DOM in the * view, otherwise <tt>false</tt>. */ // private replaceLiveRows : function(cursor, forceReplace) { var spill = cursor-this.lastRowIndex; if (spill == 0 && forceReplace !== true) { return; } // decide wether to prepend or append rows // if spill is negative, we are scrolling up. Thus we have to prepend // rows. If spill is positive, we have to append the buffers data. var append = spill > 0; // abs spill for simplyfiying append/prepend calculations spill = Math.abs(spill); // adjust cursor to the buffered model index var cursorBuffer = cursor-this.bufferRange[0]; // we can skip checking for append or prepend if the spill is larger than // visibleRows. We can paint the whole rows new then- if (spill >= this.visibleRows || spill == 0) { this.mainBody.update(this.renderRows( cursorBuffer, cursorBuffer+this.visibleRows-1 )); } else { if (append) { this.removeRows(0, spill-1); var html = this.renderRows(cursorBuffer+this.visibleRows-spill, cursorBuffer+this.visibleRows-1); Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html); } else { this.removeRows(this.visibleRows-spill, this.visibleRows-1); var html = this.renderRows(cursorBuffer, cursorBuffer+spill-1); Ext.DomHelper.insertHtml('beforeBegin', this.mainBody.dom.firstChild, html); } } this.processRows(0, undefined, true); this.lastRowIndex = cursor; }, /** * Adjusts the scroller height to make sure each row in the dataset will be * can be displayed, no matter which value the current height of the grid * component equals to. */ // protected adjustBufferInset : function() { var g = this.grid, ds = g.store; var c = g.getGridEl(); var scrollbar = this.cm.getTotalWidth()+this.scrollOffset > c.getSize().width; // adjust the height of the scrollbar this.liveScroller.dom.style.height = this.liveScroller.dom.parentNode.offsetHeight + (Ext.isGecko ? ((ds.totalLength > 0 && scrollbar) ? - this.horizontalScrollOffset : 0) : (((ds.totalLength > 0 && scrollbar) ? 0 : this.horizontalScrollOffset)))+"px"; if (this.rowHeight == -1) { return; } if (ds.totalLength <= this.visibleRows) { this.liveScrollerInset.style.height = "0px"; return; } var height = this.rowHeight*ds.totalLength; height += (c.getSize().height-(this.visibleRows*this.rowHeight)); if (scrollbar) { height -= this.horizontalScrollOffset; } this.liveScrollerInset.style.height = (height)+"px"; }, /** * Recomputes the number of visible rows in the table based upon the height * of the component. The method adjusts the <tt>rowIndex</tt> property as * needed, if the sum of visible rows and the current row index exceeds the * number of total data available. */ // protected adjustVisibleRows : function() { if (this.rowHeight == -1) { if (this.getRows()[0]) { this.rowHeight = this.getRows()[0].offsetHeight; } else { return; } } var g = this.grid, ds = g.store; var c = g.getGridEl(); var cm = this.cm; var size = c.getSize(true); var vh = size.height; var vw = size.width-this.scrollOffset; // horizontal scrollbar shown? if (cm.getTotalWidth() > vw) { // yes! vh -= this.horizontalScrollOffset; } vh -= this.mainHd.getHeight(); var visibleRows = Math.max(1, Math.floor(vh/this.rowHeight)); if (this.visibleRows == visibleRows) { return; } this.visibleRows = visibleRows; var totalLength = ds.totalLength; if (this.rowIndex + visibleRows > totalLength) { this.rowIndex = Math.max(0, ds.totalLength-this.visibleRows); this.lastRowIndex = this.rowIndex; this.updateLiveRows(this.rowIndex, true); } else { this.updateLiveRows(this.rowIndex, true); } }, adjustScrollerPos : function(pixels, suspendEvent) { var liveScroller = this.liveScroller; if (suspendEvent === true) { liveScroller.un('scroll', this.onLiveScroll, this); } liveScroller.dom.scrollTop += pixels; if (suspendEvent === true) { liveScroller.dom.scrollTop = liveScroller.dom.scrollTop; liveScroller.on('scroll', this.onLiveScroll, this); } } });